#include "MapDrawer.h"
#include "GFuns.h"
#include "Scaler.h"
#include "WorldMap.h"
#include "Weather.h"
#include "TextDrawer.h"
#include "Scaler.h"
#include "Game.h"

#include "ParticleSystem.h"

//*********************************************
//* CORE DRAWER FUNCTIONS
//*********************************************

MapDrawer::MapDrawer(Map* aMap, int aScreenW, int aScreenH, int aTileSize)
	: _Drawer(aTileSize),
		characterDrawer(aTileSize)
{
	_Map = aMap;
	_screenW = aScreenW;
	_screenH = aScreenH;
	_panX = 0;
	_panY = 0;
	_zoom = 1;
	_tileSize = aTileSize;
	_isGrid = false;
	_isMinimap = true;
	heroLocked = true;
	justRefreshed = false;

	int flags = al_get_new_bitmap_flags();
	al_set_new_bitmap_flags(0);
	_render = al_create_bitmap(_screenW,_screenH);
	_miniMap = al_create_bitmap(_Map->_tilesX+8,_Map->_tilesY+8);
	al_set_new_bitmap_flags(flags);

	lightMap = al_create_bitmap(_screenW,_screenH);

	_renderTiles.build(_screenW/_tileSize+2,_screenH/_tileSize+2);
	for(int x=0; x<_renderTiles.getSizeW(); x++)
		for(int y=0; y<_renderTiles.getSizeH(); y++)
			_renderTiles(x,y) = al_create_bitmap(_tileSize,_tileSize);
	int moreTiles = (_screenW/_tileSize+2) > (_screenH/_tileSize+2)
								? (_screenW/_tileSize+2) : (_screenH/_tileSize+2);
	_scrollTemp.resize(moreTiles);

	prepareTransitions();

	// Big trees fake bitmaps
	bigTreeTiles.build(_Map->_tilesX,_Map->_tilesY);
	for(int x=0; x<bigTreeTiles.getSizeW(); x++)
		for(int y=0; y<bigTreeTiles.getSizeH(); y++)
			bigTreeTiles(x,y) = NULL;

	// Characters bitmaps
	for(unsigned int i=0; i<_Map->characters.size(); i++)
	{
		ALLEGRO_BITMAP* character = al_create_bitmap(_tileSize,_tileSize*1.5);
		characters.push_back(character);
		characterNames.push_back(nullptr);
	}

	refresh();
}

MapDrawer::~MapDrawer(){
	al_destroy_bitmap(_render);
	al_destroy_bitmap(lightMap);
	al_destroy_bitmap(_miniMap);
	for(int i=0; i<32; i++) al_destroy_bitmap(_transitions[i]);
	for(int x=0; x<_renderTiles.getSizeW(); x++)
		for(int y=0; y<_renderTiles.getSizeH(); y++)
			al_destroy_bitmap(_renderTiles(x,y));

	for(int x=0; x<bigTreeTiles.getSizeW(); x++)
		for(int y=0; y<bigTreeTiles.getSizeH(); y++)
			if(bigTreeTiles(x,y) != NULL)
				al_destroy_bitmap(bigTreeTiles(x,y));

	for(unsigned int i=0; i<characters.size(); i++)
		al_destroy_bitmap(characters[i]);

	for(unsigned int i=0; i<characterNames.size(); i++)
		if(characterNames[i] != nullptr) al_destroy_bitmap(characterNames[i]);
}

void MapDrawer::resize(int aScreenW, int aScreenH){
	al_destroy_bitmap(_render);
	for(int x=0; x<_renderTiles.getSizeW(); x++)
		for(int y=0; y<_renderTiles.getSizeH(); y++)
			al_destroy_bitmap(_renderTiles(x,y));

	_screenW = aScreenW;
	_screenH = aScreenH;

	int flags = al_get_new_bitmap_flags();
	al_set_new_bitmap_flags(0);
	_render = al_create_bitmap(_screenW,_screenH);
	al_set_new_bitmap_flags(flags);

	lightMap = al_create_bitmap(_screenW,_screenH);

	_renderTiles.build(_screenW/_tileSize+2,_screenH/_tileSize+2);
	for(int x=0; x<_renderTiles.getSizeW(); x++)
		for(int y=0; y<_renderTiles.getSizeH(); y++)
			_renderTiles(x,y) = al_create_bitmap(_tileSize,_tileSize);
	int moreTiles = (_screenW/_tileSize+2) > (_screenH/_tileSize+2)
								? (_screenW/_tileSize+2) : (_screenH/_tileSize+2);
	_scrollTemp.resize(moreTiles);

	pan(UP);
	pan(DOWN);
	pan(LEFT);
	pan(RIGHT);
	refresh();
}

void MapDrawer::prepareTransitions(){
	ALLEGRO_BITMAP* disp = al_get_target_bitmap();

	// Making the transitions map
	// http://www.gamedev.net/page/resources/_/technical/game-programming/tilemap-based-game-techniques-handling-terrai-r934
	int d = _tileSize/6;
	int s = _tileSize;
	ALLEGRO_COLOR transColor = color.black;
	ALLEGRO_COLOR transBg = color.white;

	for(int i=0; i<32; i++){
		_transitions[i] = al_create_bitmap(_tileSize,_tileSize);
		al_set_target_bitmap(_transitions[i]);
		al_clear_to_color(transBg);
		if(i<16){
			int x = i;
			int pos[4][4] = {{0,0,d,s},{0,0,s,d},{s-d,0,s,s},{0,s-d,s,s}};
			bool walls[4] = {0,0,0,0};
			for(int p=0; p<4; p++){
				if(x%2 == 1){
					al_draw_filled_rectangle(pos[p][0],pos[p][1],pos[p][2],pos[p][3],transColor);
					walls[p] = true;
				}
				x >>= 1;
			}
			al_draw_filled_rectangle(d,d,s-d,s-d,transColor);
			al_draw_filled_circle(s/2,s/2,s/2-d,transBg);
			if(!(walls[0] && walls[1])) al_draw_filled_rectangle(d,d,s/2,s/2,transBg);
			if(!(walls[1] && walls[2])) al_draw_filled_rectangle(s-d,d,s/2,s/2,transBg);
			if(!(walls[2] && walls[3])) al_draw_filled_rectangle(s-d,s-d,s/2,s/2,transBg);
			if(!(walls[3] && walls[0])) al_draw_filled_rectangle(d,s-d,s/2,s/2,transBg);
		}
		else{
			int x = i-16;
			int pos[4][2] = {{0,0},{s,0},{s,s},{0,s}};
			for(int p=0; p<4; p++){
				if(x%2 == 1) al_draw_filled_circle(pos[p][0],pos[p][1],d,transColor);
				x >>= 1;
			}
		}
		al_convert_mask_to_alpha(_transitions[i],color.white);

		// Blur the result
		// 1. Poor blur
		ALLEGRO_BITMAP* out = al_create_bitmap(_tileSize,_tileSize);
		blur(_transitions[i],out);
		al_set_target_bitmap(_transitions[i]);
		al_draw_bitmap(out,0,0,0);
		al_destroy_bitmap(out);

		// 2. GaussianBlur
		//gaussianBlur(_transitions[i]);

		// 3. Scale blur
		//double scale = 21.3;
		//ALLEGRO_BITMAP* scaler = al_create_bitmap(_tileSize/scale,_tileSize/scale);
		//al_set_target_bitmap(scaler);
		//al_set_new_bitmap_flags(ALLEGRO_MAG_LINEAR);
		//al_draw_scaled_bitmap(_transitions[i],0,0,_tileSize,_tileSize,0,0,_tileSize/scale,_tileSize/scale,0);

		//al_set_target_bitmap(_transitions[i]);
		//al_draw_scaled_bitmap(scaler,0,0,_tileSize/scale,_tileSize/scale,0,0,_tileSize,_tileSize,0);
		//al_destroy_bitmap(scaler);

		// Display the transition effect
		//al_set_target_bitmap(disp);
		//al_clear_to_color(color.mpink);
		//al_draw_bitmap(out,20,20,0);
		//al_draw_bitmap(_transitions[i],200,200,0);
		//al_flip_display();
		//while(i==13){};
	}
	al_set_target_bitmap(disp);
}

void MapDrawer::calculateTransitions(int aLower, int aX, int aY, int& aEdges, int& aCorners){
	int i = aX;
	int j = aY;
	int neighbor[8] = {_Map->getLower(i-1,j-1),_Map->getLower(i-1,j),_Map->getLower(i+1,j-1),_Map->getLower(i,j-1),
												_Map->getLower(i+1,j+1),_Map->getLower(i+1,j),_Map->getLower(i-1,j+1),_Map->getLower(i,j+1)};
	int l = aLower;
	int x = 0;
	if(neighbor[0] == l) x+=1;
	if(neighbor[2] == l) x+=2;
	if(neighbor[4] == l) x+=4;
	if(neighbor[6] == l) x+=8;
	aCorners = x+16;

	x=0;
	if(neighbor[1] == l) x+=1;
	if(neighbor[3] == l) x+=2;
	if(neighbor[5] == l) x+=4;
	if(neighbor[7] == l) x+=8;
	aEdges = x;
}

void MapDrawer::pan(EDir dir, int aSpeed){
	if(aSpeed == 0) aSpeed = 20/_zoom;
	int worldW = _Map->_tilesX*_tileSize-1;
	int worldH = _Map->_tilesY*_tileSize-1;
	switch(dir){
	case UP: 
		_panY -= aSpeed;
		if(_panY < 0) _panY = 0;
		break;
	case RIGHT: 
		_panX += aSpeed;
		if(_panX > worldW - _screenW) _panX = worldW - _screenW;
		break;
	case DOWN:
		_panY += aSpeed;
		if(_panY > worldH - _screenH) _panY = worldH - _screenH;
		break;
	case LEFT: 
		_panX -= aSpeed;
		if(_panX < 0) _panX = 0;
		break;
	}
}

void MapDrawer::SetPan(int aX, int aY){
	int maxW = _Map->_tilesX*_tileSize-1 - _screenW;
	int maxH = _Map->_tilesY*_tileSize-1 - _screenH;

	_panX = aX;
	_panY = aY;

	if(_panX < 0) _panX = 0;
	if(_panX > maxW) _panX = maxW;
	if(_panY < 0) _panY = 0;
	if(_panY > maxH) _panY = maxH;
}

void MapDrawer::SetHeroLock(bool aLock)
{
	if(!aLock) heroLocked = false;
	else if(!heroLocked)
	{
		heroLocked = true;
		refresh();
	}
}

void MapDrawer::mouseMiniMapPan(int aX, int aY){
	if(aX < 20 || aX > _Map->_tilesX+20 || aY < 20 || aY > _Map->_tilesY+20) return;

	int panX = (aX-20)*_tileSize;
	int panY = (aY-20)*_tileSize;

	_panX = panX;
	_panY = panY;

	pan(UP);
	pan(DOWN);
	pan(LEFT);
	pan(RIGHT);
	refresh();
	/*if(panX > _panX) pan(RIGHT,60);
	if(panX < _panX) pan(LEFT,60);
	if(panY > _panY) pan(DOWN,60);
	if(panY < _panY) pan(UP,60);*/
}

void MapDrawer::zoom(bool aZoom, float aStep){
	if(aStep < 0) aStep = 1.3;

	if(aZoom){
		if((_zoom *= aStep) >= 4) _zoom = 4;
	}
	else{
		if((_zoom /= aStep) <= 1) _zoom = 1;
	}
}

void MapDrawer::SetZoom(float aZoom)
{
	_zoom = aZoom;

	if(_zoom < 1) _zoom = 1;
	if(_zoom > 4) _zoom = 4;
}

void MapDrawer::drawGrid(int aX1, int aY1, int aX2, int aY2){
	int alpha = 20;
	static char buffer[3];

	TextDrawer::SetFont(FONT_GRID);

	for(int i=aX1; i<aX2; i++){
		int p = i*_tileSize-_panX;
		int s = _tileSize;
		al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ONE);
		al_draw_line(p,0,p,_screenH,al_map_rgb(alpha,alpha,alpha),1);
		al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);

		_itoa(i,buffer,10);
		TextDrawer::DrawString(buffer,p+s/2,5,ALIGN_CENTER);
	}
	for(int i=aY1; i<aY2; i++){
		int p = i*_tileSize-_panY;
		int s = _tileSize;
		al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ONE);
		al_draw_line(0,p,_screenW,p,al_map_rgb(alpha,alpha,alpha),1);
		al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);

		_itoa(i,buffer,10);
		TextDrawer::DrawString(buffer,5,p+s/2,ALIGN_LEFT);
	}
}

void MapDrawer::drawTile(int aX, int aY){
	PushRand(_Map->getSeed(aX,aY));
	if(aX < 0 || aY < 0 || aX >= _Map->_tilesX || aY >= _Map->_tilesY) return;

	drawLower(aX,aY);
	drawTransitions(aX,aY);
	drawUpper(aX,aY);

	PopRand();
}

void MapDrawer::scrollRender(int aDir){
	int w = _renderTiles.getSizeW();
	int h = _renderTiles.getSizeH();

	if(aDir == RIGHT){
		for(int y=0; y<h; y++)
			_scrollTemp[y] = _renderTiles(0,y);
		for(int x=0; x<w-1; x++)
			for(int y=0; y<h; y++)
				_renderTiles(x,y) = _renderTiles(x+1,y);
		for(int y=0; y<h; y++)
			_renderTiles(w-1,y) = _scrollTemp[y];
	}
	if(aDir == LEFT){
		for(int y=0; y<h; y++)
			_scrollTemp[y] = _renderTiles(w-1,y);
		for(int x=w-1; x>0; x--)
			for(int y=0; y<h; y++)
				_renderTiles(x,y) = _renderTiles(x-1,y);
		for(int y=0; y<h; y++)
			_renderTiles(0,y) = _scrollTemp[y];
	}
	if(aDir == DOWN){
		for(int x=0; x<w; x++)
			_scrollTemp[x] = _renderTiles(x,0);
		for(int y=0; y<h-1; y++)
			for(int x=0; x<w; x++)
				_renderTiles(x,y) = _renderTiles(x,y+1);
		for(int x=0; x<w; x++)
			_renderTiles(x,h-1) = _scrollTemp[x];
	}
	if(aDir == UP){
		for(int x=0; x<w; x++)
			_scrollTemp[x] = _renderTiles(x,h-1);
		for(int y=h-1; y>0; y--)
			for(int x=0; x<w; x++)
				_renderTiles(x,y) = _renderTiles(x,y-1);
		for(int x=0; x<w; x++)
			_renderTiles(x,0) = _scrollTemp[x];
	}
}

void MapDrawer::moveRender(int aX, int aY){
	unsigned int w = _renderTiles.getSizeW();
	unsigned int h = _renderTiles.getSizeH();

	if(aX > 0)
		for(int i=0; i<aX; i++)
			scrollRender(RIGHT);
	if(aX < 0)
		for(int i=0; i<-aX; i++)
			scrollRender(LEFT);
	if(aY > 0)
		for(int i=0; i<aY; i++)
			scrollRender(DOWN);
	if(aY < 0)
		for(int i=0; i<-aY; i++)
			scrollRender(UP);
}

void MapDrawer::refresh()
{
	ALLEGRO_BITMAP* disp = al_get_target_bitmap();

	if(IsHeroLocked())
	{
		float heroX = (_Map->hero->GetPositionX()+0.5)*_tileSize+_Map->hero->GetOffsetX()*_tileSize;
		float heroY = (_Map->hero->GetPositionY()+1)*_tileSize+_Map->hero->GetOffsetY()*_tileSize-_tileSize/2;
		SetPan(heroX-_screenW/2, heroY-_screenH/2);
	}

	int leftX = _panX/_tileSize;
	int topY = _panY/_tileSize;

	for(int x=0; x<_renderTiles.getSizeW(); x++)
	for(int y=0; y<_renderTiles.getSizeH(); y++)
	{
		al_set_target_bitmap(_renderTiles(x,y));
		drawTile(x+leftX,y+topY);
	}

	for(int x=0; x<bigTreeTiles.getSizeW(); x++)
	for(int y=0; y<bigTreeTiles.getSizeH(); y++)
	{
		al_destroy_bitmap(bigTreeTiles(x,y));
		bigTreeTiles(x,y) = NULL;
	}

	// Characters reload
	for(unsigned int i=0; i<characters.size(); i++)
		al_destroy_bitmap(characters[i]);

	for(unsigned int i=0; i<characterNames.size(); i++)
		if(characterNames[i] != nullptr) al_destroy_bitmap(characterNames[i]);

	characters.clear();
	characterNames.clear();
	for(unsigned int i=0; i<_Map->characters.size(); i++)
	{
		ALLEGRO_BITMAP* character = al_create_bitmap(_tileSize,_tileSize*1.5);
		characters.push_back(character);
		characterNames.push_back(nullptr);
	}

	al_set_target_bitmap(disp);
	justRefreshed = true;
	_Map->refreshed();
}

void MapDrawer::DrawQuestMarker(int aX, int aY, ALLEGRO_COLOR aColor)
{
	static int miniTick;
	miniTick++;
	static Scaler scaler(-1,1,1.0,1.3);
	float squeeze = sin(miniTick/5.0);
	float h = 5*scaler.scale(squeeze);
	draw_triangle(aX-2*h,aY,aX+2*h,aY,aX,aY+4*h,aColor,Color::mod(aColor,-96),2);
	Node n[] = {{aX,aY-h},{aX+h,aY},{aX-h,aY},{aX,aY+h}};
	draw_quad(n,aColor,Color::mod(aColor,-96),2);
}

void MapDrawer::render()
{
	ALLEGRO_COLOR c = color.white;
	ALLEGRO_BITMAP* disp = al_get_target_bitmap();

	al_set_target_bitmap(_render);
	al_clear_to_color(color.black);

	// pan the screen depending on hero position
	if(IsHeroLocked() && !justRefreshed)
	{
		float heroX = (_Map->hero->GetPositionX()+0.5)*_tileSize+_Map->hero->GetOffsetX()*_tileSize;
		float heroY = (_Map->hero->GetPositionY()+1)*_tileSize+_Map->hero->GetOffsetY()*_tileSize-_tileSize/2;
		SetPan(heroX-_screenW/2, heroY-_screenH/2);
	}

	// helpers
	int leftX = _panX/_tileSize;
	int topY = _panY/_tileSize;
	int rightX = _screenW/_tileSize+leftX+2;
	int bottomY = _screenH/_tileSize+topY+2;
	int xCorr = -_panX%_tileSize;
	int yCorr = -_panY%_tileSize;

	// draw on the render tiles matrix
	static int oLeftX, oTopY;

	if(justRefreshed)
	{
		oLeftX = leftX;
		oTopY = topY;
		justRefreshed = false;
	}

	if(leftX != oLeftX || topY != oTopY){
		int dX = leftX-oLeftX;
		int dY = topY-oTopY;

		// upadte the table if the screen has moved
		moveRender(dX,dY);

		int startI = dX > 0 ? _renderTiles.getSizeW()-dX : 0;
		int endI = dX > 0 ? _renderTiles.getSizeW() : -dX; 
		int startJ = dY > 0 ? _renderTiles.getSizeH()-dY : 0;
		int endJ = dY > 0 ? _renderTiles.getSizeH() : -dY;

		for(int i=startI; i<endI; i++){
			for(int j=0; j<_renderTiles.getSizeH(); j++){
				al_set_target_bitmap(_renderTiles(i,j));
				drawTile(i+leftX,j+topY);
			}
		}

		for(int i=0; i<_renderTiles.getSizeW(); i++){
			for(int j=startJ; j<endJ; j++){
				al_set_target_bitmap(_renderTiles(i,j));
				drawTile(i+leftX,j+topY);
			}
		}

		oLeftX = leftX;
		oTopY = topY;
	}

	// render characters to bitmaps
	TextDrawer::SetFont(FONT_CHARACTER_NAME);
	for(unsigned int i=0; i<characters.size(); i++)
	{
		Character* activeCharacter = _Map->characters[i];
		activeCharacter->SetVisibility(true);
		if(activeCharacter->GetPositionX() < leftX-1 ||
			 activeCharacter->GetPositionX() > rightX ||
			 activeCharacter->GetPositionY() < topY-1 ||
			 activeCharacter->GetPositionY() > bottomY
			)
		{
			activeCharacter->SetVisibility(false);
			continue;
		}
		if(!activeCharacter->HasChanged()) continue;

		al_set_target_bitmap(characters[i]);
		al_clear_to_color(color.blank);
		characterDrawer.Draw(activeCharacter);

		if(characterNames[i] == nullptr)
			characterNames[i] = al_create_bitmap(_tileSize*2,_tileSize/3);
		ALLEGRO_BITMAP* disp = al_get_target_bitmap();
		al_set_target_bitmap(characterNames[i]);
		al_clear_to_color(color.blank);
		TextDrawer::DrawString(activeCharacter->GetName().c_str(),al_get_bitmap_width(characterNames[i])/2,5,ALIGN_CENTER);
		al_set_target_bitmap(disp);
	}

	// render to the screen
	al_set_target_bitmap(_render);

	// tiles to the screen
	for(int y=0; y<_renderTiles.getSizeH(); y++)
		for(int x=0; x<_renderTiles.getSizeW(); x++)
				al_draw_bitmap(_renderTiles(x,y),x*_tileSize+xCorr,y*_tileSize+yCorr,0);

	// render particles
	vector<ParticleSystem*>* effects = _Map->GetEffects();
	for(unsigned int e=0; e<effects->size(); e++)
	{
		if((*effects)[e]->GetSource().x < _panX || (*effects)[e]->GetSource().x > _panX + _screenW 
		|| (*effects)[e]->GetSource().y < _panY || (*effects)[e]->GetSource().y > _panY + _screenH) continue;

		vector<Particle>* particles = (*effects)[e]->GetParticles();
		for(unsigned int p=0; p<particles->size(); p++)
		{
			Particle pr = (*particles)[p];
			ALLEGRO_COLOR col = al_map_rgba(pr.r,pr.g,pr.b,pr.a);
			al_draw_filled_circle(pr.position.x-_panX,pr.position.y-_panY,2,col);
		}
	}

	// characters to the screen
	for(unsigned int i=0; i<characters.size(); i++)
	{
		Character* activeCharacter = _Map->characters[i];
		if(!activeCharacter->IsVisible()) continue;

		// traces
		list<Trace>* traces = activeCharacter->GetTraces();
		if(traces->size() > 0)
		{
			for(list<Trace>::iterator i=traces->begin(); i!=traces->end(); ++i)
			{
				int rtx = i->position.x * _tileSize + 32;
				int rty = i->position.y * _tileSize + 32;
				al_draw_filled_circle(rtx-_panX,rty-_panY,3,al_map_rgba(0,0,0,i->alpha*255));
			}
		}

		int rx = activeCharacter->GetPositionX() - leftX;
		int ry = activeCharacter->GetPositionY() - topY;

		float realX = rx*_tileSize+xCorr+activeCharacter->GetOffsetX()*_tileSize;
		float realY = ry*_tileSize+yCorr+activeCharacter->GetOffsetY()*_tileSize-_tileSize/2;

		if(activeCharacter == Game::GetHero() && Game::GetHero()->isGhost)
			drawAlphaBitmap(characters[i],128,realX,realY,0);
		else
			al_draw_bitmap(characters[i],realX,realY,0);

		// quest marker
		for(unsigned int i=0; i<activeCharacter->quests.size(); i++)
		{
			if(activeCharacter->quests[i]->owner == activeCharacter)
			{
				EQuestStatus qs = (EQuestStatus)activeCharacter->quests[i]->progress;
				if(qs == QUEST_STATUS_NEW)
					DrawQuestMarker(realX+_tileSize/2,realY-_tileSize/2,color.leafL);
				else if(qs != QUEST_STATUS_DONE)
					DrawQuestMarker(realX+_tileSize/2,realY-_tileSize/2,color.banana);

				break;
			}
		}
	}

	// big trees to the screen
	for(int y=0; y<_renderTiles.getSizeH()+2; y++){
		for(int x=-1; x<_renderTiles.getSizeW(); x++)
		{
			int tx = leftX+x;
			int ty = topY+y-1;
			if(_Map->getUpper(tx,ty) == O_TREE)
			{
				int s = _tileSize;
				float n = 2;

				if(bigTreeTiles(tx,ty) == NULL)
				{
					bigTreeTiles(tx,ty) = al_create_bitmap(s*n,s*n);
					ALLEGRO_BITMAP* disp = al_get_target_bitmap();
					al_set_target_bitmap(bigTreeTiles(tx,ty));
					al_clear_to_color(color.blank);
					PushRand(_Map->getSeed(leftX+x,topY+y-1));
					int ts = _Drawer._tileSize;
					_Drawer._tileSize *=n;
					_Drawer.drawTree(_Map->getBiome());
					_Drawer._tileSize = ts;
					PopRand();
					al_set_target_bitmap(disp);
				}

				al_draw_bitmap(bigTreeTiles(tx,ty),x*s+xCorr -((s*n)-s)/2,(y-1)*s+yCorr -((s*n)-s),0);
			}
		}
	}

	// character names
	for(unsigned int i=0; i<characters.size(); i++)
	{
		Character* activeCharacter = _Map->characters[i];
		if(!activeCharacter->IsVisible()) continue;

		int rx = activeCharacter->GetPositionX() - leftX;
		int ry = activeCharacter->GetPositionY() - topY;

		float realX = rx*_tileSize+xCorr+activeCharacter->GetOffsetX()*_tileSize+_tileSize/2;
		float realY = ry*_tileSize+yCorr+activeCharacter->GetOffsetY()*_tileSize-_tileSize/2;

		realX-=al_get_bitmap_width(characterNames[i])/2;
		realY-=10;

		if(realY+_panY < 10) realY = 10;
		al_draw_bitmap(characterNames[i],realX,realY,0);
	}

	// light spots
	if(_Map->getMapType() == MAP_MAZE)
	{
		int op,src,dest;
		al_get_blender(&op,&src,&dest);

		ALLEGRO_BITMAP* disp = al_get_target_bitmap();
		al_set_target_bitmap(lightMap);

		ALLEGRO_COLOR shadowLevel = color.shadow;
		al_clear_to_color(shadowLevel);

		for(unsigned int i=0; i<_Map->GetLightSpots()->size(); i++)
		{
			LightSource spot = _Map->GetLightSpots()->at(i);
			if(spot.position.x+spot.radius < _panX || spot.position.x-spot.radius > _panX + _screenW 
			|| spot.position.y+spot.radius < _panY || spot.position.y-spot.radius > _panY + _screenH) continue;

			al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);

			int rads = 20;
			unsigned char alp = shadowLevel.a*255;
			for(int i=1; i<rads; i++)
			{
				al_draw_filled_circle(spot.position.x-_panX,spot.position.y-_panY,spot.radius-(spot.radius/rads*i),
					al_map_rgba(0,0,0,alp-(alp/rads*i)));
			}
		}

		al_set_target_bitmap(disp);
		al_set_blender(op,src,dest);
		al_draw_bitmap(lightMap,0,0,0);
	}

	// weather effects
	if(_Map->GetWeather() != NULL)
	{
		if(_Map->GetWeather()->GetEffect() == WEATHER_RAIN)
		{
			ALLEGRO_BITMAP* disp = al_get_target_bitmap();
			al_set_target_bitmap(lightMap);
			al_clear_to_color(color.shadowL);
			if(_Map->GetWeather()->IsLightning()) al_clear_to_color(color.lightning);
			al_set_target_bitmap(disp);
			al_draw_bitmap(lightMap,0,0,0);
		}

		vector<RainDrop>* drops = _Map->GetWeather()->GetDrops();

		RainDrop& r = (*drops)[0];
		int l;
		float len = 2.0;
		int rings = 2;

		for(unsigned int d=0; d<drops->size(); d++)
		{
			r = (*drops)[d];
			l = _Map->getLower(r.position.x/_tileSize,r.position.y/_tileSize);
			if(l != F_SOIL && l != F_MARKET) continue;

			if(_Map->GetWeather()->GetEffect() == WEATHER_RAIN)
			{
				al_draw_line(r.position.x-_panX, r.position.y-_panY,
					r.position.x-r.speed.x*len-_panX, r.position.y-r.speed.y*len-_panY, color.shadowLL, r.width);

				if(r.time > r.life-rings+1)
					for(int i=rings; i>=0; i--)
						if(r.time == r.life-i) al_draw_ellipse(r.position.x+(r.speed.x*i)-_panX, r.position.y+(r.speed.y*i)-_panY,
							2*(rings-i+1),(rings-i+1),color.shadowL,1);
			}

			if(_Map->GetWeather()->GetEffect() == WEATHER_SNOW)
			{
				al_draw_filled_circle(r.position.x-_panX, r.position.y-_panY,
					r.width, color.white);
			}
		}
	}

	// city names to the screen
	if(_Map->getMapType() == MAP_WORLD)
	{
		TextDrawer::SetFont(FONT_CITY_NAME);
		WorldMap* pMap = (WorldMap*)_Map;
		for(unsigned int i=0; i<pMap->cities.size(); i++)
		{
			int x = (pMap->cities[i].x - leftX)*_tileSize+xCorr;
			int y = (pMap->cities[i].y - topY - 0.5)*_tileSize+yCorr;

			if(x > 0 && y > 0 && x < _screenW && y < _screenH)
				TextDrawer::DrawString(pMap->citiesNames[i].c_str(),x+_tileSize/2,y,ALIGN_CENTER);
		}
	}

	// grid and minimap
	if(_isGrid) drawGrid(leftX,topY,rightX,bottomY);
	drawMinimap();

	al_set_target_bitmap(disp);
}

void MapDrawer::draw(){
	render();

	float cx = (Game::GetHero()->GetPositionX()+Game::GetHero()->GetOffsetX())*_tileSize+_tileSize/2-_panX;
	float cy = (Game::GetHero()->GetPositionY()+Game::GetHero()->GetOffsetY())*_tileSize+_tileSize/2-_panY;
	int sx = cx -(cx/_zoom);
	int sy = cy -(cy/_zoom);
	int sw = _screenW/_zoom;
	int sh = _screenH/_zoom;
	int dx = 0;
	int dy = 0;
	int dw = _screenW;
	int dh = _screenH;
	al_draw_scaled_bitmap(_render,sx,sy,sw,sh,dx,dy,dw,dh,0);
	if(Game::GetHero()->intoxication > 0)
	{
		static int miniTicker;
		miniTicker++;
		static Scaler scaler(-1,1,-20,20);
		int sqx = scaler.scale(sin(miniTicker/50.0))*Game::GetHero()->intoxication;
		int sqy = scaler.scale(cos(miniTicker/50.0))*Game::GetHero()->intoxication;

		drawScaledAlphaBitmap(_render,128,sx,sy,sw,sh,dx+sqx,dy+sqy,dw,dh,0);
	}


	static float scaleMin = 1;
	float mmMargin = 20;
	float mmW = al_get_bitmap_width(_miniMap);
	float mmH = al_get_bitmap_height(_miniMap);
	float maxScale = (_screenH-mmMargin*2)/mmH;
	if(_isMinimap)
	{
		if(scaleMin > 1) scaleMin -= (scaleMin - 1.0)/4;
		if(scaleMin < 1) scaleMin = 1.0;
		al_draw_scaled_bitmap(_miniMap, 0,0,mmW,mmH,
			mmMargin,mmMargin,mmW*scaleMin,mmH*scaleMin,0);
	}
	else{
		if(scaleMin < maxScale) scaleMin += (maxScale - scaleMin)/4;
		if(scaleMin > maxScale) scaleMin = maxScale;
		al_draw_scaled_bitmap(_miniMap, 0,0,mmW,mmH,
			mmMargin,mmMargin,mmW*scaleMin,mmH*scaleMin,0);
	}
}

//*********************************************
//* CRITICAL FUNCTIONS
//*********************************************

void MapDrawer::drawTransitions(int aX, int aY){
	static ALLEGRO_BITMAP* foreground = al_create_bitmap(_tileSize,_tileSize);
	ALLEGRO_BITMAP* disp = al_get_target_bitmap();

	int end;
	if(_Map->getMapType() == MAP_WORLD) end = BIOME_END;
	if(_Map->getMapType() == MAP_CITY) end = FLOOR_END;
	if(_Map->getMapType() == MAP_MAZE) end = FLOOR_END;

	for(int trans=_Map->getLower(aX,aY)+1; trans<end; trans++){
		int edges, corners;
		calculateTransitions(trans,aX,aY,edges,corners);
		if(!(edges == 0 && corners == 16)){

			al_set_target_bitmap(foreground);
			for(int d=0; d<8; d++)
				if(_Map->getLower(aX+dir_sqr[d][0], aY+dir_sqr[d][1]) == trans){
					drawLower(aX+dir_sqr[d][0], aY+dir_sqr[d][1]);
					break;
				}

			_Drawer.mixBitmaps(foreground,_transitions[edges],_transitions[corners]);

			al_set_target_bitmap(disp);
			al_draw_bitmap(foreground,0,0,0);
		}
	}
}

void MapDrawer::drawLower(int aX, int aY){
	if(_Map->getMapType() == MAP_WORLD) drawLowerWorld(aX,aY);
	if(_Map->getMapType() == MAP_CITY) drawLowerCity(aX,aY);
	if(_Map->getMapType() == MAP_MAZE) drawLowerCity(aX,aY);
}

void MapDrawer::drawUpper(int aX, int aY){
	if(_Map->getMapType() == MAP_WORLD) drawUpperWorld(aX,aY);
	if(_Map->getMapType() == MAP_CITY) drawUpperCity(aX,aY);
	if(_Map->getMapType() == MAP_MAZE) drawUpperCity(aX,aY);
}

void MapDrawer::drawMinimap(){
	ALLEGRO_BITMAP* disp = al_get_target_bitmap();

	al_set_target_bitmap(_miniMap);
	al_lock_bitmap(_miniMap,ALLEGRO_PIXEL_FORMAT_ANY,ALLEGRO_LOCK_WRITEONLY);
	for(int j=0; j<_Map->_tilesY; j++){
		for(int i=0; i<_Map->_tilesX; i++){

			ALLEGRO_COLOR c;
			if(_Map->getMapType() == MAP_WORLD) c = getMinimapColorWorld(_Map->getTile(i,j));
			if(_Map->getMapType() == MAP_CITY) c = getMinimapColorCity(_Map->getTile(i,j),(EBiome)_Map->getBiome());
			if(_Map->getMapType() == MAP_MAZE) c = getMinimapColorCity(_Map->getTile(i,j),(EBiome)_Map->getBiome());
			al_put_pixel(i+4,j+4,c);

		}
	}
	al_unlock_bitmap(_miniMap);

	// quests markers
	static int miniTicker;
	miniTicker++;
	static Scaler scaler(-1,1,1,3);
	float r = scaler.scale(sin(miniTicker/10.0));

	for(unsigned int c=0; c<_Map->characters.size(); c++)
		for(unsigned int q=0; q<_Map->characters[c]->quests.size(); q++)
		{
			if(_Map->characters[c]->quests[q]->owner == _Map->characters[c])
			{
				EQuestStatus qs = (EQuestStatus)_Map->characters[c]->quests[q]->progress;
				if(qs != QUEST_STATUS_DONE)
					draw_ellipse(_Map->characters[c]->GetPositionX()+4,_Map->characters[c]->GetPositionY()+4,r,r,color.banana,color.bananaDD,1);

				break;
			}
		}

	int x = (_panX/_tileSize+_screenW/_tileSize + _panX/_tileSize)/2;
	int y = (_panY/_tileSize+_screenH/_tileSize + _panY/_tileSize)/2;
	int hX = Game::GetHero()->GetPositionX();
	int hY = Game::GetHero()->GetPositionY();
	al_put_pixel(hX+4,hY+4,color.white);
	al_draw_rectangle(_panX/_tileSize+5,_panY/_tileSize+5,
		_panX/_tileSize+_screenW/_tileSize+5,_panY/_tileSize+_screenH/_tileSize+5,
		color.carpetD,1);

	al_draw_rectangle(1,1,_Map->_tilesX+8,_Map->_tilesY+8,color.woodDD,1);
	al_draw_rectangle(2,2,_Map->_tilesX+7,_Map->_tilesY+7,color.woodL,1);
	al_draw_rectangle(3,3,_Map->_tilesX+6,_Map->_tilesY+6,color.wood,1);
	al_draw_rectangle(4,4,_Map->_tilesX+5,_Map->_tilesY+5,color.woodD,1);

	al_set_target_bitmap(disp);
}

//*********************************************
//* WORLD MAP SPECIFIC
//*********************************************

void MapDrawer::drawLowerWorld(int aX, int aY){
	_Drawer.drawBiome(_Map->getLower(aX,aY));
}

void MapDrawer::drawUpperWorld(int aX, int aY){
	int lower = _Map->getLower(aX,aY);
	int upper = _Map->getUpper(aX,aY);

	if(upper == RIVER){
		bool connections[4] = {0,0,0,0};
		for(int d=0; d<4; d++){
			int tr = _Map->getUpper(aX+dir[d][0],aY+dir[d][1]);
			if(tr == TNONE || tr == RIVER || tr == BRIDGE || tr == CONFLUENCE) connections[d] = true;
		}

		_Drawer.drawRiver(connections);
	}
	if(upper == ROAD || upper == CITY){
		bool connections[4] = {0,0,0,0};
		for(int d=0; d<4; d++){
			int tr = _Map->getUpper(aX+dir[d][0],aY+dir[d][1]);
			if(tr == CITY || tr == ROAD || tr == BRIDGE || tr == PORT) connections[d] = true;
		}
		_Drawer.drawRoad(connections);
	}
	if(upper == BRIDGE){
		if(_Map->getUpper(aX,aY-1) == RIVER) _Drawer.drawBridge(false);
		else _Drawer.drawBridge(true);
	}
	if(upper == CONFLUENCE){
		for(int d=0; d<4; d++) if(_Map->getUpper(aX+dir[d][0],aY+dir[d][1]) == RIVER) _Drawer.drawConfluence(d);
	}
	if(upper == TREE) _Drawer.drawTree(lower);
	if(upper == MOUNTAIN) _Drawer.drawMountain(lower);
	if(upper == CITY) _Drawer.drawCity(lower);
	if(upper == PORT){
		bool connections[4] = {0,0,0,0};
		for(int d=0; d<4; d++){
			int tr = _Map->getUpper(aX+dir[d][0],aY+dir[d][1]);
			if(tr == CITY || tr == ROAD || tr == BRIDGE || tr == PORT) connections[d] = true;
		}
		_Drawer.drawPort(connections);
	}
}

ALLEGRO_COLOR MapDrawer::getMinimapColorWorld(Tile aTile){
	int lower = aTile.lower;
	int upper = aTile.upper;

	switch(upper){
	case CITY: return color.black;
	case RIVER: return color.waterL;
	case ROAD: return color.iron;
	}

	switch(lower){
	case OCEAN: return color.water;
	case LAKE: return color.waterD;
	case COAST: return color.sand;
	case SAVANNA: return color.sandD;
	case DESERT:	return color.sand;
	case STEPPE:  return color.sandL;
	case TROPIC: return color.grassD;
	case FOREST:	return color.grass;
	case SCLEROPHYLL:  return color.grassL;
	case TAIGA: return color.taiga;
	case TUNDRA:	return color.tundra;
	case POLAR:  return color.snow;
	default: return color.white;
	}
}

//*********************************************
//* CITY MAP SPECIFIC
//*********************************************

void MapDrawer::drawLowerCity(int aX, int aY){
	int lower = _Map->getLower(aX,aY);

	int s = _tileSize;

	if(lower == F_SOIL) _Drawer.drawBiome(_Map->_biome);
	if(lower == F_WOOD) _Drawer.drawWoodenFloor();
	if(lower == F_CARPET) _Drawer.drawCarpetFloor();
	if(lower == F_MARKET) _Drawer.drawMarketFloor();
	if(lower == F_STONE) _Drawer.drawMarketFloor();
	//if(lower == F_STONE) _Drawer.drawTempleFloor();
}

void MapDrawer::drawUpperCity(int aX, int aY){
	int lower = _Map->getLower(aX,aY);
	int upper = _Map->getUpper(aX,aY);
	int s = _tileSize;

	//if(upper == O_TREE) _Drawer.drawTree(_Map->getBiome()); // big tree trick
	if(upper == O_ROAD){
		bool connections[4] = {0,0,0,0};
		for(int d=0; d<4; d++){
			int tr = _Map->getUpper(aX+dir[d][0],aY+dir[d][1]);
			if(tr == O_ROAD) connections[d] = true;
		}
		_Drawer.drawRoad(connections, _tileSize);
	}
	if(upper == O_WALL || upper == O_TORCH || upper == O_LADDER){
		bool connections[4] = {0,0,0,0};
		for(int d=0; d<4; d++){
			int tr = _Map->getUpper(aX+dir[d][0],aY+dir[d][1]);
			if(tr == O_WALL || tr == O_TORCH || tr == O_LADDER) connections[d] = true;
		}
		_Drawer.drawWall(connections, _Map->getBiome());

		if(upper == O_TORCH) _Drawer.drawTorch();
		if(upper == O_LADDER) _Drawer.drawLadder();
	}

	int doubleTiles[6] = {O_BED, O_OVEN, O_BOOKCASE, O_FURNACE, O_HERBSTAND, O_RECEPTION};
	for(int i=0; i<6; i++){
		if(upper == doubleTiles[i]){
			bool begin = false;
			int dir;

			if(_Map->getUpper(aX+1,aY) == doubleTiles[i]){
				begin = true;
				if(_Map->getUpper(aX,aY+1) == O_WALL) dir = UP;
				else dir = DOWN;
			}
			if(_Map->getUpper(aX,aY+1) == doubleTiles[i]){
				begin = true;
				if(_Map->getUpper(aX+1,aY) == O_WALL) dir = LEFT;
				else dir = RIGHT;
			}
			if(_Map->getUpper(aX-1,aY) == doubleTiles[i]){
				if(_Map->getUpper(aX,aY+1) == O_WALL) dir = UP;
				else dir = DOWN;
			}
			if(_Map->getUpper(aX,aY-1) == doubleTiles[i]){
				if(_Map->getUpper(aX+1,aY) == O_WALL) dir = LEFT;
				else dir = RIGHT;
			}

			switch(doubleTiles[i]){
			case O_BED: _Drawer.drawBed(begin,dir); break;
			case O_OVEN: _Drawer.drawOven(begin,dir); break;
			case O_FURNACE: _Drawer.drawFurnace(begin,dir); break;
			case O_BOOKCASE: _Drawer.drawBookcase(begin,dir); break;
			case O_HERBSTAND: _Drawer.drawHerbstand(begin,dir); break;
			case O_RECEPTION: _Drawer.drawReception(begin,dir); break;
			}
		}
	} 
	if(upper == O_ALTAR){
		bool dirs[4] = {0,0,0,0};
		int cons = 0;
		for(int d=0; d<4; d++)
			if(_Map->getUpper(aX+dir[d][0],aY+dir[d][1]) == O_ALTAR){
				dirs[d] = true;
				cons++;
			}

		if(cons == 2){
			if(dirs[0]) _Drawer.drawAltar(true, LEFT);
			else _Drawer.drawAltar(true, UP);
		}
		else{
			if(dirs[0]) _Drawer.drawAltar(false, RIGHT);
			if(dirs[1]) _Drawer.drawAltar(false, LEFT);
			if(dirs[2]) _Drawer.drawAltar(false, DOWN);
			if(dirs[3]) _Drawer.drawAltar(false, UP);
		}
	}
	if(upper == O_TABLE) _Drawer.drawTable();
	if(upper == O_CHAIR) _Drawer.drawTable(true);
	if(upper == O_WARDROBE){
		if(_Map->getUpper(aX-1,aY) == O_WALL) _Drawer.drawWardrobe(RIGHT);
		else if(_Map->getUpper(aX+1,aY) == O_WALL) _Drawer.drawWardrobe(LEFT);
		else if(_Map->getUpper(aX,aY+1) == O_WALL) _Drawer.drawWardrobe(UP);
		else if(_Map->getUpper(aX,aY-1) == O_WALL) _Drawer.drawWardrobe(DOWN);
	}
	if(upper == O_BARREL) _Drawer.drawBarrel();
	if(upper == O_CRATE) _Drawer.drawCrate();
	if(upper == O_CHEST) _Drawer.drawChest();
	if(upper == O_BUCKET) _Drawer.drawBucket();
	if(upper == O_ANVIL) _Drawer.drawAnvil();
	if(upper == O_POT) _Drawer.drawPot();
	if(upper == O_BENCH){
		if(_Map->getUpper(aX-1,aY) == O_BENCH || _Map->getUpper(aX+1,aY) == O_BENCH)
			_Drawer.drawBench(false);
		else if(_Map->getUpper(aX,aY-1) == O_BENCH || _Map->getUpper(aX,aY+1) == O_BENCH)
			_Drawer.drawBench(true);
	}
	if(upper == O_CANDLESTICK) _Drawer.drawCandlestick();
	if(upper == O_PLANT) _Drawer.drawPlant();
	if(upper == O_SIGN_WEAPON) _Drawer.drawSignWeapon();
	if(upper == O_SIGN_ARMOR) _Drawer.drawSignArmor();
	if(upper == O_SIGN_HERB) _Drawer.drawSignHerbs();
	if(upper == O_SIGN_INN) _Drawer.drawSignInn();
	if(upper == O_GRAVE) _Drawer.drawGrave();
	if(upper == O_FENCE){
		bool dirs[] = {0,0,0,0};
		for(int d=0; d<4; d++)
			if(_Map->getUpper(aX+dir[d][0],aY+dir[d][1]) == O_FENCE)
				dirs[d] = true;
		_Drawer.drawFence(dirs);
	}
	if(upper == O_WELL) _Drawer.drawWell();
	if(upper == O_MARKETSTAND) _Drawer.drawMarketStand();
	if(upper == O_THRONE){
		if(_Map->getUpper(aX-1,aY-1) == O_CANDLESTICK){
			if(_Map->getUpper(aX+1,aY-1) == O_CANDLESTICK) _Drawer.drawThrone(DOWN);
			else _Drawer.drawThrone(RIGHT);
		}
		else{
			if(_Map->getUpper(aX-1,aY+1) == O_CANDLESTICK) _Drawer.drawThrone(UP);
			else _Drawer.drawThrone(LEFT);
		}
	}

	if(upper == O_ARMOR) _Drawer.drawArmor();
}

ALLEGRO_COLOR MapDrawer::getMinimapColorCity(Tile aTile, EBiome aBiome){
	int lower = aTile.lower;
	int upper = aTile.upper;

	switch(upper){
	case O_WALL: return color.mountainD;
	case O_TORCH: return color.mountainDD;
	case O_LADDER: return color.wood;
	case O_ROAD: return color.road;
	case O_GRAVE: return color.mountainD;
	case O_TREE: return color.leafD;
	}

	switch(lower){
	case F_WOOD: return color.bridge;
	case F_STONE: return color.mountainLL;
	case F_CARPET: return color.roof;
	case F_MARKET: return color.metal;
	case F_SOIL: {
		switch(aBiome){
		case OCEAN: return color.water;
		case LAKE: return color.waterD;
		case COAST: return color.sand;
		case SAVANNA: return color.sandD;
		case DESERT:	return color.sand;
		case STEPPE:  return color.sandL;
		case TROPIC: return color.grassD;
		case FOREST:	return color.grass;
		case SCLEROPHYLL:  return color.grassL;
		case TAIGA: return color.taiga;
		case TUNDRA:	return color.tundra;
		case POLAR:  return color.snow;
		}
							 }
	default: return color.white;
	}
}